from django.shortcuts import render, HttpResponse, redirect
from app01.myfroms import MyRegForm
from app01 import models
from django.http import JsonResponse
import random
from django.contrib import auth
from django.contrib.auth.decorators import login_required  # 校验用户登录装饰器
from django.db.models import Count
from django.db.models.functions import TruncMonth
import json
from django.db.models import F
from django.db import transaction
from utils.mypage import Pagination     # 自定义分页器
from bs4 import BeautifulSoup
import os
from DjangoBBS import settings


def get_random():
    return random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)


def register(request):
    form_obj = MyRegForm()
    if request.method == 'POST':
        # 校验数据是否合法
        form_obj = MyRegForm(request.POST)  # 使用Forms组件校验
        back_dic = {"code": 1000, 'msg': ""}
        if form_obj.is_valid():
            # {'username': 'jason', 'password': '1234', 'confirm_password': '1234', 'email': '2213447497@qq.com'}
            clean_date = form_obj.cleaned_data  # 将校验通过的数据字典赋值给一个变量
            # 将字典里面的confirm_password键值对删除
            clean_date.pop('confirm_password')
            # 用户头像
            file_obj = request.FILES.get('avatar')
            ''' 针对用户头像一定要判断是否传值不能直接添加到字典里面去 '''
            if file_obj:
                clean_date['avatar'] = file_obj
            # 直接使用字典传数据
            models.UserInfo.objects.create_user(**clean_date)
            back_dic['url'] = '/login/'
        else:
            back_dic['code'] = 2000
            # print(form_obj.add_errors)
            back_dic['msg'] = form_obj.errors

        print(back_dic)
        return JsonResponse(back_dic)
    return render(request, 'register.html', locals())


'''
Form.errors 
返回一个ErrorDict实例,包含当前表单所有的错误,可以在is_valid之前调用

Form.has_error(field, code=None)
返回指定field是否有错误

Form.add_error(field, error) 
向指定field添加一个错误

Form.non_field_errors()
不属于field的错误,如数据库中有复合主键重复等.
'''


def login(request):
    # is_ajax
    if request.method == 'POST':
        back_dic = {"code": 1000, 'msg': ""}
        username = request.POST.get('username')
        password = request.POST.get('password')
        code = request.POST.get('code')
        # 1先校验验证码是否正确   自己决定是否忽略    统一转大写或者小写再比较(这里忽略大小写)
        if request.session.get('code').upper() == code.upper():
            # 2校验用户名和密码是否正确
            user_obj = auth.authenticate(request, username=username, password=password)
            if user_obj:
                # 保存用户状态
                auth.login(request, user_obj)
                back_dic['url'] = '/home/'
            else:
                back_dic['code'] = 2000
                back_dic['msg'] = '用户名或密码错误'
        else:
            back_dic['code'] = 2000
            back_dic['msg'] = '验证码错误'
        return JsonResponse(back_dic)
    return render(request, 'login.html')


"""
图片相关的模块:
    pip3 install pillow
Image:生成图片, 
ImageDraw:能够在图片上乱涂乱画, 
ImageFile:控制字体样式
"""
from PIL import Image, ImageDraw, ImageFont

"""
内存管理器模块
BytesI0:临时帮你存储数据返回的时候数据是二进制|
StringIO:临时帮你存储数据返回的时候数据是字符串|
"""
from io import BytesIO, StringIO


def get_code(request):
    # 推导步骤1: 直接获取后端现成的图片二进制数据发送给前端
    # with open(r'static/img/111.png', 'rb')as f:
    #     data = f.read()

    # 推导步骤2:利用pilLow模块动态产生图片
    # img_obj = Image.new('RGB', (400, 34), get_random())
    # # 先将图片对象保存起来
    # with open(r'xxx.png', 'wb')as f:
    #     img_obj.save(f, 'png')
    # # 再将图片对象读取出来
    # with open('xxx.png', 'rb')as f:
    #     data = f.read()
    # return HttpResponse(data)

    # 推导步骤3:文件存储繁琐I0操作效率低 借助于内存管理器模块\
    # img_obj = Image.new('RGB', (400, 34), get_random())
    # io_obj = BytesIO()  # 生成一个内存管理器对象你可 以看成是文件句柄
    # img_obj.save(io_obj, 'png')
    # return HttpResponse(io_obj.getvalue())  # 从内存管理器中读取二进制的图片数据返回给前端

    # 推到步骤4:写图片验证码
    img_obj = Image.new('RGB', (400, 34), get_random())
    img_draw = ImageDraw.Draw(img_obj)  # 产生一个画笔对象
    img_font = ImageFont.truetype('static/font/荆南麦圆体.ttf', 30)  # 字体样式, 字体大小

    # 随机验证码 五位数的随机验证码   数字  小写字母    大写字母
    code = ''
    n = 5
    while n > 0:
        random_upper = chr(random.randint(65, 90))
        random_lower = chr(random.randint(97, 122))
        random_int = chr(random.randint(48, 57))
        # 从上面随机选择一个
        tmp = random.choice([random_lower, random_upper, random_int])
        # 将产生的随机字符串写入到图片上
        """
        为什么一个个写而不是生成好了之后再写
            因为一个个写能够控制每个字体的间隙而生成好之后再写的话 间隙就没法控制了
        """
        img_draw.text((300 - n * 50, 0), tmp, get_random(), img_font)
        code += tmp
        n -= 1
    print(code)
    # 随机验证码在登陆的视图函数里面需要用到 要比对 所以要找地方存起来并且其他视图函数也能拿到
    request.session['code'] = code
    io_obj = BytesIO()
    img_obj.save(io_obj, 'png')
    return HttpResponse(io_obj.getvalue())


def home(request):
    # 查询本网站所有的文章数据展示的前端页面这里可以使用分页器做分页但是我不做了你们自己课下加
    # 查询文章并传到页面上
    back_dic = {"code": 1000, 'msg':'查询不到内容!!!'}   # 先提前定义返回给编辑器的数据格式
    # 内容搜索
    page_num = request.GET.get('page', 1)
    if request.method == 'POST':
        # 先校验当前用户名是否存在
        # user_obj = models.UserInfo.objects.filter(username=request.user.username).first()
        # blog = user_obj.blog
        query_content = request.POST.get('query_content')
        # if not query_content:
        #     if request.query:
        #         query_content = request.query
        if query_content:
            # 检索文章名字
            article_queryset = models.Article.objects.filter(title__icontains=query_content)   # 忽略大小写
            # 检索用户名是否存在
            user_obj_list = models.UserInfo.objects.filter(username__icontains=query_content)   # 忽略大小写
            for user_obj in user_obj_list:
                blog = user_obj.blog
                article_list = models.Article.objects.filter(blog=blog)     # 侧边栏的筛选其实就是对article_ List再进一步筛选
                article_queryset = article_queryset | article_list      # 将两个集合合并
            if article_queryset:
                back_dic["code"] = 1002
                back_dic['msg'] = '查询成功'
                page_num = 1
                request.query = query_content
            else:
                back_dic["code"] = 1001
                back_dic['msg'] = '查询失败'
        else:
            article_queryset = models.Article.objects.all()
    else:
        article_queryset = models.Article.objects.all()
    page_obj = Pagination(current_page=page_num, all_count=article_queryset.count(), per_page_num=10)
    article_queryset = article_queryset[page_obj.start:page_obj.end]
    return render(request, 'home.html', locals())


@login_required
def set_password(request):  # 修改密码
    if request.is_ajax():
        back_dic = {'code': 1000, 'msg': ''}
        if request.method == 'POST':
            old_password = request.POST.get('old_password')
            new_password = request.POST.get('new_password')
            confirm_password = request.POST.get('confirm_password')
            is_right = request.user.check_password(old_password)
            if is_right:
                if new_password == confirm_password:
                    request.user.set_password(new_password)
                    request.user.save()  # 保存
                    back_dic['msg'] = '修改成功'
                else:
                    back_dic['code'] = 1001
                    back_dic['msg'] = '确认密码有出入'
            else:
                back_dic['code'] = 1002
                back_dic['msg'] = '原密码错误'
        return JsonResponse(back_dic)


@login_required
def logout(request):  # 退出登录
    auth.logout(request)
    return redirect('/home/')


def site(request, username, **kwargs):
    """
    # 用户界面编写
    :param request:
    :param username:
    :param kwargs: 如果该参数有值也就意味着需要对article_list做额外的筛选操作
    :return:
    """
    # 先校验当前用户名是否存在
    user_obj = models.UserInfo.objects.filter(username=username).first()
    # print(kwargs)
    # 用户如果不存返回一个404页面
    if not user_obj:
        return render(request, 'errors.html')
    page_num = request.GET.get('page', 1)
    blog = user_obj.blog
    # print('blog.site_title:', blog.sist_title) 这里属性拼写错误导致找不到
    # 查询当前个人站点下的所有的文章
    article_list = models.Article.objects.filter(blog=blog)     # 侧边栏的筛选其实就是对article_ List再进一步筛选
    if kwargs:
        # print(kwargs)   # {'condition': 'tag', 'param': '1'}
        condition = kwargs.get('condition')
        param = kwargs.get('param')
        # 判断用户想要的操作 ||
        if condition == 'category':
            article_list = article_list.filter(category_id=param)   # 筛选目录id是param的文章
        elif condition == 'tag':
            article_list = article_list.filter(tags__id=param)   # 筛选目录id是param的文章
        else:
            year, month = param.split('-')
            article_list = article_list.filter(create_time__year=year, create_time__month=month)   # 筛选目录id是param的文章
        # if not article_list:
        #     return render(request, 'errors.html')

    # # 1 查询当前用户所有的分类及分类下的文章数
    # category_list = models.Category.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list('name', 'count_num', 'pk')
    # # print(category_list)    # <QuerySet [('jason的分类一', 2), ('jason的分类二', 1), ('jason的分类三', 1)]>
    # # 2 查询当前用户所有的标签及标签下的文章数
    # tag_list = models.Tag.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list('name', 'count_num', 'pk')
    # # print(tag_list)     # <QuerySet [('python算法', 1), ('python笔记', 2), ('cdl的分类一', 2)]>
    # # 3 按照年月统计所有的文章
    # date_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values('month').annotate(count_num=Count('pk')).values_list('month', 'count_num')
    page_obj = Pagination(current_page=request.GET.get('page', 1), all_count=article_list.count(), per_page_num=10)
    article_list = article_list[page_obj.start:page_obj.end]
    return render(request, 'site.html', locals())


# 获取当前 文章所有的评论内容
def article_detail(request, article_id, username):
    # 首先验证用户名与文章id是否存在
    # 验证用户
    user_obj = models.UserInfo.objects.filter(username=username).first()
    if not user_obj:
        return render(request, 'errors.html')
    # 验证文章id 先获取文章对象    (注意文章跟作者对应)
    article_obj = models.Article.objects.filter(pk=article_id, blog__userinfo__username=username).first()
    if not article_obj:
        return render(request, 'errors.html')
    # 传站点信息
    blog = user_obj.blog
    # 获取当前 文章所有的评论内容
    comment_list = models.Comment.objects.filter(article=article_obj)
    return render(request, 'article_detail.html', locals())
    # return HttpResponse(f'文章详情页: article_id{article_id}, username{username}')


def up_or_down(request):
    """
    1.校验用户是否登陆
    2.判断当前文章是否是当前用户自己写的(自己不能点自己的文章)
    3.当前用户是否已经给当前文章点过了
    4.操作数据库
    :param request:
    :return:
    """
    if request.is_ajax():
        back_dic = {'code': 1000, 'msg': ''}
        # 1.先判断当前用户是否登陆
        # print(request.user)
        # 在3.1.1等版本中，is_authenticated是方法，因此上述判断需要改为.is_authenticated是属性:
        if request.user.is_authenticated:
            article_id = request.POST.get('article_id')     # 主键值
            is_up = request.POST.get('is_up')               # 赞还是踩
            # print('article_id', article_id, type(article_id)) # <class 'str'>
            # print('is_up', is_up, type(is_up))    # true <class 'str'>
            is_up = json.loads(is_up)   # 字符串转布尔
            # print('is_up', is_up, type(is_up))    # True <class 'bool'>
            # 2.判断当前文章是否是当前用户自己写的
            # 根据文章id查询文章对象 根据文章对象查作者 拿作者对象跟request.user对比
            article_obj = models.Article.objects.filter(pk=article_id).first()
            # 如果不是
            if not article_obj.blog.userinfo == request.user:
                # 校验3
                is_click = models.UpAndDown.objects.filter(user=request.user, article=article_obj)
                # 条件全部满足开始操作数据库并返回值
                if not is_click:
                    # 注意操作数据库记录时数据要同步操作普通字段
                    # 判断当前用户点了赞还是踩从而决定给哪个字段加一
                    if is_up:
                        # 给点赞数加一
                        models.Article.objects.filter(pk=article_id).update(up_num=F('up_num') + 1)
                        back_dic['msg'] = '点赞成功'
                    else:
                        # 给点踩数加一
                        models.Article.objects.filter(pk=article_id).update(up_num=F('down_num') + 1)
                        back_dic['msg'] = '点赞成功'
                    # 操作点赞点踩表
                    models.UpAndDown.objects.create(user=request.user, article=article_obj, is_up=is_up)
                else:
                    back_dic['code'] = 1001
                    back_dic['msg'] = '你已经点过了,不能再点'    # 这里你可以做的更加的详细提示用户到底点了赞还是点了踩
            else:
                back_dic['code'] = 1002
                back_dic['msg'] = '不能给自己点哦'    # 这里你可以做的更加的详细提示用户到底点了赞还是点了踩
        else:
            back_dic['code'] = 1003
            back_dic['msg'] = '请先<a href="/login/">登录</a>'    # 这里你可以做的更加的详细提示用户到底点了赞还是点了踩
        return JsonResponse(back_dic)


# 评论
def comment(request):
    if request.is_ajax():
        back_dic = {'code': 1000, 'msg': ""}
        if request.method == 'POST':
            # 判断用户是否登录
            if request.user.is_authenticated:
                article_id = request.POST.get('article_id')
                content = request.POST.get('content')
                parent_id = request.POST.get('parent_id')
                # print(request.POST)
                # print(article_id, content, parent_id)
                # 直接操作评论表 存储数据 两张表
                # 操作两张表-使用事务
                with transaction.atomic():
                    models.Article.objects.filter(pk=article_id).update(comment_num=F('comment_num') + 1)
                    models.Comment.objects.create(user=request.user, article_id=article_id, content=content, parent_id=parent_id)
                back_dic['msg'] = '评论成功'
            else:
                back_dic['code'] = 1001
                back_dic['msg'] = '用户未登录'
            return JsonResponse(back_dic)


# 后台管理逻辑
@login_required
def backend(request):
    # print(request.user.blog)
    article_list = models.Article.objects.filter(blog=request.user.blog)
    page_obj = Pagination(current_page=request.GET.get('page', 1), all_count=article_list.count(),per_page_num=10)
    page_queryset = article_list[page_obj.start:page_obj.end]
    return render(request, 'backend/backend.html', locals())


@login_required
def add_article(request):
    if request.method == 'POST':
        # 获取前端数据
        title = request.POST.get('title')
        content = request.POST.get('content')
        category_id = request.POST.get("category")
        tag_id_list = request.POST.getlist('tag')
        """方法1 手动切除多余内容
        # 文章简介
        # 先简单窃取文章前150个字符
        import re
        patt = re.compile('<.*?>|&emsp;')
        desc = re.sub(patt, ' ', content)[0:150]
        if not title:
            title = desc.lstrip(' \n\t').split('\n')[0]"""
        """方法2 利用bs4模块切除多余内容"""
        # 模块使用
        soup = BeautifulSoup(content, "html.parser")
        # 获取所有标签
        tags = soup.find_all()
        # print(tags) # 所有标签
        for tag in tags:
            # tag.name 就是所有标签(按顺序)
            # 针对script直接删除
            if tag.name == 'script':
                # 删除标签
                tag.decompose()
        # 截取文本150个字符当简介
        desc = soup.text[0:150]
        if not title:
            import re
            patt = re.compile('[.?!！。❓]')
            title = re.split(patt, desc)[0]
        # 添加到文章表
        article_obj = models.Article.objects.create(
            title=title,
            content=str(soup),
            desc=desc,
            category_id=category_id,
            blog=request.user.blog
        )
        # 文章和标签的关系表是我们自己创建的 没法使用add set remove cLear方法
        # 需要自己操作关系表  可能存在一次性创建多条数据 批量插入bulk_create()
        article_obj_list = []
        for i in tag_id_list:
            # 生成对象,添加到列表
            article_obj_list.append(models.Article2Tag(article=article_obj, tag_id=i))
        # 批量插入数据 .bulk_create()
        models.Article2Tag.objects.bulk_create(article_obj_list)
        # 跳转到后台管理页面
        return redirect('/backend/')
    # 获取当前用户所有分类
    category_list = models.Category.objects.filter(blog=request.user.blog)
    # 获取当前用户所有标签
    tag_list = models.Tag.objects.filter(blog=request.user.blog)
    # 先返回一个添加文章页面
    return render(request, 'backend/add_article.html', locals())


def upload_image(request):
    back_dic = {"error": 0, }   # 先提前定义返回给编辑器的数据格式
    # 框架 返回格式有要求
    # 用户写文章上传的图片也算静态资源也应该放在media文件夹下
    if request.method == 'POST':

        file_obj = request.FILES.get('imgFile')     # 打印看到了get健固定叫imgFile
        # 手动拼接文件储存文件
        file_dir = os.path.join(settings.BASE_DIR, 'media', 'article_img')
        # 优化操作 先判断当前文件夹是否存在不存在 自动创建
        if not os.path.isdir(file_dir):
            os.mkdir(file_dir)  # 创建文件夹
        # 拼接图片的完整路径
        file_path = os.path.join(file_dir, file_obj.name)
        with open(file_path, 'wb')as f:
            for line in file_obj:
                f.write(line)
        back_dic['url'] = '/media/article_img/%s' % file_obj.name
    return JsonResponse(back_dic)


# 修改头像
@login_required
def set_avatar(request):
    if request.method == 'POST':
        file_obj = request.FILES.get('avatar')
        # models.UserInfo.objects.filter(pk=request.user.pk).update(avatar=file_obj)  # 不会再自动加avatar前缀
        # 1.自己手动加前缀
        # 2.换一种方式更新
        user_obj = request.user
        user_obj.avatar = file_obj
        user_obj.save()
        return redirect('/home/')
    blog = request.user.blog
    username = request.user.username
    return render(request, 'set_avatar.html', locals())


# 删除文章
def del_article(request):
    if request.is_ajax():
        back_dic = {'code': 1000, 'msg': ""}
        if request.method == 'POST':
            # 判断用户是否登录
            if request.user.is_authenticated:
                username = request.POST.get('username')
                article_pk = request.POST.get('article_pk')
                user_name = models.Article.objects.filter(pk=article_pk).values('blog__userinfo__username').first()
                # return print(user_name)
                if username != user_name['blog__userinfo__username']:
                    back_dic['code'] = 1002
                    back_dic['msg'] = '用户与作者不符'
                else:
                    models.Article.objects.filter(pk=article_pk).delete()
                    back_dic['msg'] = '删除成功'
            else:
                back_dic['code'] = 1001
                back_dic['msg'] = '用户未登录'
            return JsonResponse(back_dic)
    return render(request, 'errors.html')


# 编辑文章
def edit_article(request):
    return render(request, 'backend/edit_article.html',locals())


# 添加分类
@login_required
def add_category(request):
    back_dic = {'code': 1000, 'msg': ""}
    if request.is_ajax():
        if request.method == 'POST':
            name = request.POST.get('name')
            category_obj = models.Category.objects.filter(name=name)
            if not category_obj:
                models.Category.objects.create(name=name, blog=request.user.blog)
                back_dic['msg'] = '创建成功'
            else:
                back_dic['code'] = 1003
                back_dic['msg'] = '创建失败,分类已存在'
        else:
            back_dic['code'] = 1001
            back_dic['msg'] = '创建失败,请求来源有问题'
    else:
        back_dic['code'] = 1002
        back_dic['msg'] = '创建失败,请求来源有问题'
    return JsonResponse(back_dic)


# 添加标签
@login_required
def add_tag(request):
    back_dic = {'code': 1000, 'msg': ""}
    if request.is_ajax():
        if request.method == 'POST':
            name = request.POST.get('name')
            tag_obj = models.Tag.objects.filter(name=name)
            if not tag_obj:
                models.Tag.objects.create(name=name, blog=request.user.blog)
                back_dic['msg'] = '创建成功'
            else:
                back_dic['code'] = 1003
                back_dic['msg'] = '创建失败,标签已存在'
        else:
            back_dic['code'] = 1001
            back_dic['msg'] = '创建失败,请求来源有问题'
    else:
        back_dic['code'] = 1002
        back_dic['msg'] = '创建失败,请求来源有问题'
    return JsonResponse(back_dic)